package c2.cross.business.minecraft;

import c2.cross.business.CrossBody;
import c2.cross.business.base.CrossModule;
import c2.cross.business.base.CrossSubModule;
import c2.cross.business.minecraft.core.MinecraftCrossMessagingModule;
import c2.cross.business.minecraft.core.MinecraftCrossReceiveModule;
import c2.cross.business.minecraft.core.MinecraftWheelManager;
import c2.cross.business.minecraft.data.MinecraftMainKV;
import cate.common.game.mail.MailCmd;
import cate.common.table.d.GDMail;
import cate.common.table.d.GDOperation;
import cate.common.table.item.hero.row.HeroBaseRow;
import cate.common.util.TimeTool;
import cate.game.db.mongo.kv.KvMongo;
import cate.game.mail.po.MailBuilder;
import cate.game.pvp.minecraft.MinecraftConstants;
import cate.game.pvp.minecraft.MinecraftMessagingModule.*;
import cate.game.pvp.minecraft.data.MinecraftCollectionPointData;
import cate.game.pvp.minecraft.data.MinecraftLayerData;
import cate.game.pvp.minecraft.event.MinecraftNotificationEvent;
import cate.game.pvp.minecraft.event.MinecraftStatusEvent;
import cate.game.pvp.minecraft.vo.MinecraftCollectionPointVO;
import cate.game.res.MixRes;
import cate.game.role.RoleSnapshot;
import cate.game.role.mail.Mail;
import cate.game.role.pvp.minecraft.msg.*;
import cate.game.util.GameKit;
import cp.solution.business.game.component.NoticeParam;
import easy.java.dev.note.NoteField;
import easy.java.practice.errorcode.EcResult;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import static cate.game.role.pvp.minecraft.MinecraftMineAreaFTC.FIGHT_TYPE_MONSTER;
import static cate.game.role.pvp.minecraft.MinecraftMineAreaFTC.FIGHT_TYPE_PLAYER;

@Slf4j
public class MinecraftModule extends CrossModule<MinecraftMainKV> {
    @Getter
    @NoteField("单线程")
    private final ScheduledExecutorService executor;
    @Getter
    @NoteField("消息发送模块")
    private final MinecraftCrossMessagingModule module = new MinecraftCrossMessagingModule();
    @NoteField("消息接受模块")
    private final MinecraftCrossReceiveModule controller = new MinecraftCrossReceiveModule();
    @NoteField("时间轮触发器")
    private final MinecraftWheelManager wheel = new MinecraftWheelManager();

    @NoteField("下次触发时间")
    private long nextTriggerTime;

    public MinecraftModule(CrossBody context) {
        super(context);
        executor = Executors.newSingleThreadScheduledExecutor();
    }

    @Override
    protected String getStatusKey() {
        return KvMongo.KEY_MINECRAFT_GAME;
    }

    @Override
    protected CrossSubModule<?>[] getSubModules() {
        return new CrossSubModule[]{controller, module};
    }

    @Override
    protected Class<MinecraftMainKV> getStatusClass() {
        return MinecraftMainKV.class;
    }

    @Override
    public String getName() {
        return "灵能矿洞";
    }

    @Override
    public void initDependent() throws Exception {
        super.initDependent();
        wheel.startUp();
        executor.execute(() -> {
            try {
                initSelf();
            } catch (Exception e) {
                log.error("", e);
            }
        });
    }

    @Override
    protected void onShutdownAfterSubModules() {
        wheel.shutDown();
    }

    @Override
    protected void onInterval() {
        long nowTime = System.currentTimeMillis();
        if (nowTime < nextTriggerTime) {
            return;
        }
        executor.execute(this::switchStatus);
        // 检测保护到期
        executor.execute(() -> status.roleProtectTimeMap.entrySet().removeIf(e -> e.getValue() <= nowTime));
    }

    /**
     * 切换状态
     */
    private void switchStatus() {
        long nowTime = System.currentTimeMillis();
        long openTime = TimeTool.getZeroMondayTimeStamp(nowTime) + TimeTool.TimeCons.WEEK;
        long closeTime = openTime - MinecraftConstants.MINECRAFT_WEEKLY_CLOSE_MINUTES * TimeTool.TimeCons.MINUTE;
        boolean close = false;
        if (closeTime <= nowTime) {
            closeTime += TimeTool.TimeCons.WEEK;
            close = true;
        }
        status.close = close;
        status.openTime = openTime;
        status.closeTime = closeTime;
        module.dispatchStatusEvent(new MinecraftStatusEvent(close, openTime, closeTime));
        nextTriggerTime = Math.min(openTime, closeTime);
        log.info("[MinecraftModule] 刷新了活动状态, close={}, openTime={}, closeTime={}, nextTriggerTime={}",
                close, openTime, closeTime, nextTriggerTime);
        if (close) {
            onClose();
        }
    }

    /**
     * 创建定时器
     *
     * @param token   Token
     * @param seconds 执行延迟秒数
     * @param task    任务
     */
    private void createTimer(String token, long seconds, Runnable task) {
//        log.info("[Timer] 添加了一个定时器, " + seconds + "秒后执行, Token=" + token);
        wheel.createTimer(token, seconds, () -> executor.submit(() -> {
            try {
                task.run();
                removeTimer(token, null);
            } catch (Exception e) {
                log.error("", e);
            }
        }));
    }

    /**
     * 移除定时器
     *
     * @param token Token
     */
    private void removeTimer(String token, String reason) {
        if (StringUtils.isNotBlank(reason)) {
            log.info("[Timer] 移除了一个定时器, Token=" + token + " reason=" + reason);
        }
        wheel.removeTimer(token);
    }

    /**
     * 获取玩家保护结束时间
     *
     * @param uid 角色UID
     */
    public long getRoleProtectTime(String uid) {
        return status.roleProtectTimeMap.getOrDefault(uid, 0L);
    }

    /**
     * 根据点位获取保护时间
     *
     * @param data 点位数据
     */
    private long getRoleProtectTime(MinecraftCollectionPointData data) {
        if (data.getGuardPlayer() == null) {
            return 0;
        }
        return getRoleProtectTime(data.getGuardPlayer().uid);
    }

    /**
     * 单个点位信息同步
     *
     * @param data 点位数据
     */
    public void updateSinglePointVO(MinecraftCollectionPointData data) {
        module.dispatchNotificationEvent(
                new MinecraftNotificationEvent(data.toVO(false, getRoleProtectTime(data))));
    }

    /**
     * 初始化计算挂机奖励
     */
    public void initSelf() {
        // 加载常量
        MinecraftConstants.loadFromTable(context.table.common);
        // 加载配置
        status.initialize(context.table);
        // 切换一下状态
        if (status.openTime == 0) {
            switchStatus();
        } else {
            nextTriggerTime = Math.min(status.openTime, status.closeTime);
        }
        // 自动挂机
        status.layers.values().forEach(layerData ->
                layerData.layerPoints.values().forEach(pointData -> calcHangReward(pointData, true)));
    }

    /**
     * 结算挂机奖励
     *
     * @param pointData 点位数据
     * @param init      是否初始化
     */
    private void calcHangReward(MinecraftCollectionPointData pointData, boolean init) {
        long seconds = pointData.calcHangReward(context.table.minecraft, context.table.drop);
        if (seconds > 0) {
            String token = GameKit.uid();
            pointData.setHangToken(token);
            createTimer(token, seconds, () -> calcHangReward(pointData, false));
        } else {
            pointData.setHangToken(null);
            //log.info(pointData + " 强制结算奖励!");
            String roleUid = null;
            int srvId = 0;
            if (pointData.getGuardPlayer() != null) {
                roleUid = pointData.getGuardPlayer().uid;
                srvId = pointData.getGuardPlayer().base.srvId;
            }
            forceSettlePoint(pointData);
            if (roleUid != null) {
                syncUsedInfo(roleUid, srvId);
            }
        }
        if (!init) {
            updateSinglePointVO(pointData);
        }
    }

    /**
     * 获取点位数据
     *
     * @param layer 层数
     * @param tid   配置ID
     */
    public MinecraftCollectionPointData getPointData(int layer, int tid) {
        MinecraftLayerData layerData = status.layers.get(layer);
        if (layerData != null) {
            return layerData.layerPoints.get(tid);
        }
        return null;
    }

    /**
     * 游戏服连接
     *
     * @param srvId 服务器ID
     */
    public void onGameConnect(int srvId) {
        module.sendBackMineAreaState(srvId, status.close,
                status.openTime, status.closeTime);
    }

    /**
     * 矿区概览
     *
     * @return 回复包
     */
    public MinecraftMineAreaOverviewResp overview() {
        MinecraftMineAreaOverviewResp resp = new MinecraftMineAreaOverviewResp();
        resp.layerToMines = new HashMap<>();
        status.layers.values().forEach(layer -> resp.layerToMines.put(layer.layer,
                (int) layer.layerPoints.values().stream().filter(pointData -> pointData.getGuardPlayer() != null).count()));
        return resp;
    }

    /**
     * 查看单层数据
     *
     * @param params 参数
     * @return 查看结果
     */
    public EcResult<MinecraftMineAreaLayerInfoResp> viewLayer(CrossViewLayerParams params) {
        EcResult<MinecraftMineAreaLayerInfoResp> r = new EcResult<>();
        if (!status.layers.containsKey(params.layer)) {
            return r.fail("查看的层不存在");
        }
        r.data = new MinecraftMineAreaLayerInfoResp();
        r.data.points = new HashMap<>();
        status.layers.get(params.layer).layerPoints.forEach((key, value) -> r.data.points.put(key,
                value.toVO(false, getRoleProtectTime(value))));
        return r.success();
    }

    /**
     * 查看我的点位
     *
     * @param roleUid 角色UID
     * @param srvId   服务器ID
     * @param detail  是否细节
     */
    public void viewMyPoints(String roleUid, int srvId, boolean detail) {
        List<MinecraftCollectionPointData> points = findRoleHoldingPoints(roleUid);
        List<MinecraftCollectionPointVO> vos = new ArrayList<>(points.size());
        points.forEach(pointData -> vos.add(pointData.toVO(detail, getRoleProtectTime(pointData))));
        MinecraftMineAreaMyPointsResp resp = new MinecraftMineAreaMyPointsResp(detail, vos);
        module.sendBackViewMyPoints(roleUid, srvId, resp);
    }

    /**
     * 查看点位信息
     *
     * @param layer 层数
     * @param tid   配置ID
     * @return 查看结果
     */
    public EcResult<MinecraftMineAreaPointDetailResp> viewPointDetail(int layer, int tid) {
        EcResult<MinecraftMineAreaPointDetailResp> r = new EcResult<>();
        MinecraftCollectionPointData pointData = getPointData(layer, tid);
        if (pointData == null) {
            return r.fail("点位不存在");
        }
        r.data = new MinecraftMineAreaPointDetailResp();
        r.data.vo = pointData.toVO(true, getRoleProtectTime(pointData));
        return r.success();
    }

    /**
     * 获取当前角色占领的点位
     *
     * @param roleUid 角色唯一ID
     */
    public List<MinecraftCollectionPointData> findRoleHoldingPoints(String roleUid) {
        List<MinecraftCollectionPointData> list = new ArrayList<>();
        for (MinecraftLayerData layerData : status.layers.values()) {
            layerData.layerPoints.values().stream().filter(pointData -> pointData.getGuardPlayer() != null
                            && pointData.getGuardPlayer().base != null
                            && roleUid.equals(pointData.getGuardPlayer().base.uid)
                            && pointData.getGuards() != null)
                    .forEach(list::add);
        }
        return list;
    }

    /**
     * 获取当前角色占用信息
     *
     * @param roleUid 角色唯一ID
     * @param srvId   服务器ID
     */
    public CrossRoleUsedInfo getCrossRoleUsedInfo(String roleUid, int srvId) {
        List<Integer> usedHallows = new ArrayList<>();
        List<Integer> usedHeroProtoIds = new ArrayList<>();
        findRoleHoldingPoints(roleUid).forEach(pointData -> pointData.getGuards().forEach(playSide -> {
            if (playSide.hallow != null) {
                usedHallows.add(playSide.hallow.type);
            }
            playSide.hs.forEach(battleHero -> {
                HeroBaseRow config = battleHero.hero.getTpl(context.play.getPlayBuildContext());
                if (config != null) {
                    usedHeroProtoIds.add(config.protoId);
                }
            });
        }));
        return CrossRoleUsedInfo.of(roleUid, srvId, usedHallows, usedHeroProtoIds);
    }

    /**
     * 同步角色占用信息
     *
     * @param roleUid 角色唯一ID
     * @param srvId   服务器ID
     */
    public void syncUsedInfo(String roleUid, int srvId) {
        module.syncRoleUsedInfo(getCrossRoleUsedInfo(roleUid, srvId));
    }

    /**
     * 活动关闭
     */
    public void onClose() {
        log.info("活动关闭触发全体结算");
        executor.submit(() -> {
            Map<String, RoleSnapshot> roles = new HashMap<>();
            status.layers.values().forEach(layer ->
                    layer.layerPoints.values().forEach(pointData -> {
                        if (pointData.getGuardPlayer() != null && !roles.containsKey(pointData.getGuardPlayer().uid)) {
                            roles.put(pointData.getGuardPlayer().uid, pointData.getGuardPlayer());
                        }
                        forceSettlePoint(pointData);
                    }));
            roles.values().forEach(roleSnapshot -> {
                module.syncRoleUsedInfo(CrossRoleUsedInfo.of(roleSnapshot.uid, roleSnapshot.base.srvId,
                        new ArrayList<>(), new ArrayList<>()));
                module.sendBackViewMyPoints(roleSnapshot.uid, roleSnapshot.base.srvId,
                        new MinecraftMineAreaMyPointsResp(true, new ArrayList<>()));
            });
        });
    }

    /**
     * 强制结算点位
     *
     * @param pointData 点位数据
     */
    public void forceSettlePoint(MinecraftCollectionPointData pointData) {
        if (pointData.getGuardPlayer() != null) {
            RoleSnapshot guardPlayer = pointData.getGuardPlayer();
            MixRes rewards = pointData.forceSettle();
            // 发送邮件
            MailCmd mail = new MailCmd()
                    .withContent(GDMail.Tid.MINECRAFT_SETTLE_BACK, new ArrayList<>())
                    .withReward(rewards.toItemsStr())
                    .withOpr(GDOperation.MINECRAFT_MINE_AREA_HANG_REWARD);
            module.sendAddMail(guardPlayer.uid, guardPlayer.base.srvId, mail);
        }
        pointData.clear();
        updateSinglePointVO(pointData);
    }

    /**
     * 合服结算矿点
     *
     * @param srvId 服务器ID
     * @return 结算的邮件
     */
    public MinecraftMergeMails mergeSettlePoint(int srvId) {
        List<CrossAddMail> retList = new ArrayList<>();
        status.layers.values().forEach(layer ->
                layer.layerPoints.values().forEach(pointData -> {
                    try {
                        if (pointData.getGuardPlayer() != null && pointData.getGuardPlayer().base.srvId == srvId) {
                            if (pointData.isFighting()) {
                                String token = pointData.cancelFighting();
                                if (token != null) {
                                    removeTimer(token, "强制结算移除战斗定时器");
                                }
                            }
                            RoleSnapshot guardPlayer = pointData.getGuardPlayer();
                            MixRes rewards = pointData.forceSettle();
                            // 发送邮件
                            Mail mail = new MailBuilder()
                                    .setContentId(GDMail.Tid.MINECRAFT_SETTLE_BACK)
                                    .setNoticeParamList(new ArrayList<>())
                                    .setReward(rewards)
                                    .setOperation(GDOperation.MINECRAFT_MINE_AREA_HANG_REWARD)
                                    .build();
                            retList.add(CrossAddMail.of(guardPlayer.uid, guardPlayer.base.srvId, mail));
                            pointData.clear();
                            updateSinglePointVO(pointData);
                        }
                    } catch (Exception e) {
                        log.error("", e);
                    }
                }));
        return MinecraftMergeMails.of(retList);
    }

    /**
     * 尝试战斗返回
     *
     * @param params 请求参数
     */
    public CrossTryFightPointBack tryFightPointBack(CrossTryFightPointParams params) {
        EcResult<MinecraftCollectionPointData> r = tryFightPoint(params);
        return CrossTryFightPointBack.of(params.roleSnapshot.uid,
                params.roleSnapshot.base.srvId,
                params.layer,
                params.tid,
                r.ok() ? new EcResult<Void>().success() : new EcResult<Void>().fail(r.message),
                r.ok() ? r.data.getGuards() : null,
                r.ok() ? r.data.getHeroStates() : null);
    }

    /**
     * 尝试战斗
     *
     * @param params 请求参数
     * @return 尝试结果
     */
    public EcResult<MinecraftCollectionPointData> tryFightPoint(CrossTryFightPointParams params) {
        EcResult<MinecraftCollectionPointData> r = new EcResult<>();
        if (status.close) {
            return r.fail("当前无法战斗");
        }
        List<MinecraftCollectionPointData> list = findRoleHoldingPoints(params.roleSnapshot.uid);
        if (list.size() >= MinecraftConstants.MAX_HOLDING_POINT_NUM) {
            return r.fail("占领点位数上限");
        }
        MinecraftCollectionPointData pointData = getPointData(params.layer, params.tid);
        if (pointData == null) {
            return r.fail("点位不存在");
        }
        r.data = pointData;
        if (pointData.fighting) {
            return r.fail("点位正在战斗中");
        }
        if (pointData.getGuardPlayer() != null && pointData.getGuardPlayer().uid.equals(params.roleSnapshot.uid)) {
            return r.fail("不可挑战自己的点位");
        }
        if (getRoleProtectTime(pointData) > System.currentTimeMillis()) {
            return r.fail("点位保护中");
        }
        String token = pointData.beginFight(params.roleSnapshot);
        // 定时器强制结束战斗
        createTimer(token, MinecraftConstants.FIGHT_TIMEOUT_SECONDS, () -> {
            if (token.equals(pointData.fightToken)) {
                log.info("[CTRL]" + pointData + " 强制结束战斗状态!");
                pointData.cancelFighting();
            }
        });
        // 更新点位信息
        updateSinglePointVO(pointData);
        return r.success();
    }

    /**
     * 处理战斗结果
     *
     * @param params 参数
     * @return 处理返回
     */
    public byte handleFightResultReport(CrossFightResultReportParams params, MixRes lostRes, EcResult<MinecraftMineAreaFightResultResp> r) {
        r.data = params.resultResp;
        MinecraftCollectionPointData pointData = getPointData(params.layer, params.tid);
        if (pointData == null || !pointData.fighting) {
            r.fail("找不到点位");
            return 0;
        }
        if (status.close) {
            // 清除战斗状态 移除定时器
            String token = pointData.cancelFighting();
            if (token != null) {
                removeTimer(token, "战斗结算移除强制定时器");
            }
            r.fail("当前无法战斗");
            return 0;
        }
        RoleSnapshot oldGuardPlayer = null;
        byte fightType = pointData.getGuardPlayer() == null ? FIGHT_TYPE_MONSTER : FIGHT_TYPE_PLAYER;
        if (params.win && r.data != null) {
            long nowTime = System.currentTimeMillis();
            // 攻打成功
            // 点位更换防守方
            if (pointData.getGuardPlayer() == null) {
                // 本来没有防守方 结算打怪
                // 奖励为打怪奖励
                r.data.reward = new MixRes(pointData.getConfig(context.table.minecraft).monsterRewardStr);
            } else {
                oldGuardPlayer = pointData.getGuardPlayer();
                // 有防守方 进行一个奖励的抢
                MixRes oldMailRes = new MixRes();
                if (r.data.reward == null) {
                    r.data.reward = new MixRes();
                }
                MixRes remainRes = new MixRes();
                pointData.calcRob(context.table.minecraft, oldMailRes, r.data.reward, lostRes, remainRes);
                // 给战败方发奖励
                log.info(oldGuardPlayer.base.name +
                        "(" + oldGuardPlayer.uid + ") 战败失守 " + pointData);
                if (!oldMailRes.isEmpty()) {
                    List<NoticeParam> paramList = new ArrayList<>();
                    paramList.add(new NoticeParam(NoticeParam.NoticeType.STRING, params.roleSnapshot.base.name));
                    paramList.add(new NoticeParam(NoticeParam.NoticeType.MIX_RES, lostRes));
//                    paramList.add(new NoticeParam(NoticeParam.NoticeType.MIX_RES, remainRes));
                    MailCmd mail = new MailCmd()
                            .withContent(GDMail.Tid.MINECRAFT_ROB_BACK, paramList)
                            .withReward(oldMailRes.toItemsStr())
                            .withOpr(GDOperation.MINECRAFT_MINE_AREA_HANG_REWARD);
                    module.sendAddMail(oldGuardPlayer.uid, oldGuardPlayer.base.srvId, mail);
                }
                // 占领敌方矿点 进入锁定时间
                pointData.setLockedTime(nowTime + MinecraftConstants.POINT_LOCK_TIME_MS);
            }
            log.info(params.roleSnapshot.base.name +
                    "(" + params.roleSnapshot.uid + ") 占领了 " + pointData);

            pointData.setGuardPlayer(params.roleSnapshot);
            pointData.setGuards(params.playSides);
            pointData.setHeroStates(new ArrayList<>());

            pointData.setHoldTime(nowTime);
            pointData.setLastHangGetTime((nowTime / 1000) * 1000);
            pointData.setLastHangCalcTime((nowTime / 1000) * 1000);
//            pointData.setProtectTime(0);

            if (pointData.getHangToken() != null) {
                removeTimer(pointData.getHangToken(), "矿点易主移除老定时器");
            }
            pointData.setHangToken(GameKit.uid());
            createTimer(pointData.getHangToken(), 60, () -> calcHangReward(pointData, false));
        } else {
            // 攻打失败
            // 如果打的是人 更新防守方血量
            if (pointData.getGuardPlayer() != null) {
                pointData.setHeroStates(params.heroStates);
            }
        }
        // 清除战斗状态 移除定时器
        String token = pointData.cancelFighting();
        if (token != null) {
            removeTimer(token, "战斗结算移除强制定时器");
        }
        // 更新占用信息
        syncUsedInfo(params.roleSnapshot.uid, params.roleSnapshot.base.srvId);
        viewMyPoints(params.roleSnapshot.uid, params.roleSnapshot.base.srvId, false);
        // 更新战败方点位信息和占用信息
        if (oldGuardPlayer != null) {
            syncUsedInfo(oldGuardPlayer.uid, oldGuardPlayer.base.srvId);
            viewMyPoints(oldGuardPlayer.uid, oldGuardPlayer.base.srvId, false);
        }
        if (fightType == FIGHT_TYPE_PLAYER) {
            status.roleProtectTimeMap.remove(params.roleSnapshot.uid);
            // 更新信息
            findRoleHoldingPoints(params.roleSnapshot.uid).forEach(this::updateSinglePointVO);
        }
        updateSinglePointVO(pointData);
        r.success();
        return fightType;
    }

    /**
     * 领取所有点位挂机奖励
     *
     * @param roleUid 角色ID
     * @return 领取结果
     */
    public EcResult<MixRes> getAllHangReward(String roleUid) {
        EcResult<MixRes> r = new EcResult<>();
        List<MinecraftCollectionPointData> myPoints = findRoleHoldingPoints(roleUid);
        if (CollectionUtils.isEmpty(myPoints)) {
            return r.fail("没有可领取的点位");
        }
        r.data = new MixRes();
        for (MinecraftCollectionPointData pointData : myPoints) {
            MixRes pointRes = pointData.getHangReward();
            if (!pointRes.isEmpty()) {
                r.data.addList(pointRes);
                if (pointData.getHangToken() != null) {
                    removeTimer(pointData.getHangToken(), "收菜移除老定时器1");
                }
                String token = GameKit.uid();
                pointData.setHangToken(token);
                createTimer(pointData.getHangToken(), 60, () -> calcHangReward(pointData, false));
            }
        }
        if (r.data.isEmpty()) {
            return r.fail("没有可领取奖励");
        }
        myPoints.forEach(this::updateSinglePointVO);
        return r.success();
    }

    /**
     * 领取挂机奖励
     *
     * @param roleUid 角色UID
     * @param layer   层数
     * @param tid     配置ID
     * @return 领取结果
     */
    public EcResult<MixRes> getHangReward(String roleUid, int layer, int tid) {
        EcResult<MixRes> r = new EcResult<>();
        MinecraftCollectionPointData pointData = getPointData(layer, tid);
        if (pointData == null) {
            return r.fail("点位不存在");
        }
        if (pointData.getGuardPlayer() == null || !pointData.getGuardPlayer().uid.equals(roleUid)) {
            return r.fail("不是自己的点位");
        }
        r.data = pointData.getHangReward();
        if (r.data.isEmpty()) {
            return r.fail("没有可领取奖励");
        }
        if (pointData.getHangToken() != null) {
            removeTimer(pointData.getHangToken(), "收菜移除老定时器2");
        }
        String token = GameKit.uid();
        pointData.setHangToken(token);
        createTimer(pointData.getHangToken(), 60, () -> calcHangReward(pointData, false));
        // 更新点位信息
        updateSinglePointVO(pointData);
        return r.success();
    }

    /**
     * 离开点位
     *
     * @param roleUid 角色UID
     * @param layer   层数
     * @param tid     配置ID
     * @return 离开结果
     */
    public EcResult<MixRes> giveUpPoint(String roleUid, int srvId, int layer, int tid) {
        EcResult<MixRes> r = new EcResult<>();
        MinecraftCollectionPointData pointData = getPointData(layer, tid);
        if (pointData == null) {
            return r.fail("点位不存在");
        }
        if (pointData.getGuardPlayer() == null || !pointData.getGuardPlayer().uid.equals(roleUid)) {
            return r.fail("不是自己的点位");
        }
//        if (pointData.getLockedTime() > System.currentTimeMillis()) {
//            return r.fail("点位静默中无法撤离");
//        }
        r.data = pointData.forceSettle();
        pointData.clear();
        // 更新点位信息
        syncUsedInfo(roleUid, srvId);
        updateSinglePointVO(pointData);
        viewMyPoints(roleUid, srvId, false);
        return r.success();
    }

    /**
     * 保护矿点
     *
     * @param roleUid 角色UID
     * @return 处理结果
     */
    public EcResult<Long> protectPoint(String roleUid) {
        EcResult<Long> r = new EcResult<>();
        if (status.close) {
            return r.fail("活动关闭中");
        }
        long nowTime = System.currentTimeMillis();
        long oldTime = getRoleProtectTime(roleUid);
        long endTime = Math.max(nowTime, oldTime) + MinecraftConstants.PROTECT_HOUR * TimeTool.TimeCons.HOUR;
        status.roleProtectTimeMap.put(roleUid, endTime);
        r.data = endTime;
        // 更新信息
        List<MinecraftCollectionPointData> myPoints = findRoleHoldingPoints(roleUid);
        myPoints.forEach(this::updateSinglePointVO);
        return r.success();
    }

    /**
     * GM生成挂机奖励
     *
     * @param layer  层数
     * @param tid    配置ID
     * @param minute 分钟数
     */
    public void GMHangReward(int layer, int tid, int minute) {
        MinecraftCollectionPointData pointData = getPointData(layer, tid);
        if (pointData != null) {
            log.info("[GM] 点位:" + layer + "层" + tid + "点 生成了" + minute + "分钟挂机奖励!");
            pointData.calcHangRewardByMinutes(context.table.minecraft, context.table.drop, minute);
            updateSinglePointVO(pointData);
        } else {
            log.info("[GM] 点位:" + layer + "层" + tid + "点 没找到...");
        }
    }

    /**
     * GM取消锁定状态
     *
     * @param layer 层数
     * @param tid   配置ID
     */
    public void GMCancelLock(int layer, int tid) {
        MinecraftCollectionPointData pointData = getPointData(layer, tid);
        if (pointData != null) {
            log.info("[GM] 点位:" + layer + "层" + tid + "点 取消锁定!");
            pointData.setLockedTime(0);
            updateSinglePointVO(pointData);
        } else {
            log.info("[GM] 点位:" + layer + "层" + tid + "点 没找到...");
        }
    }

    /**
     * GM存储
     */
    public void GMSave() {
        log.info("尝试存储...");
        saveStatus();
        log.info("存储完毕");
    }
}
